Skip to content

Comments

Added logic for keeping background process runing on closing of debug…#299

Open
rohitneharabrowserstack wants to merge 2 commits intomasterfrom
ENGG-5314_background_debug
Open

Added logic for keeping background process runing on closing of debug…#299
rohitneharabrowserstack wants to merge 2 commits intomasterfrom
ENGG-5314_background_debug

Conversation

@rohitneharabrowserstack
Copy link
Contributor

@rohitneharabrowserstack rohitneharabrowserstack commented Feb 20, 2026

… console

Summary by CodeRabbit

  • New Features

    • Background window now hides on close instead of exiting, preventing accidental closure during normal use.
  • Bug Fixes

    • More reliable shutdown: added a timed fallback to ensure background window is cleaned up on quit.
    • Improved safeguards so the background window is only destroyed during intentional quit, reducing unexpected terminations.

@linear
Copy link

linear bot commented Feb 20, 2026

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

Walkthrough

Adds controlled background-window lifecycle handling: startBackgroundProcess.js installs a close handler that hides the window, overrides and preserves the original destroy to only allow destruction during quit mode, and ensures the close handler is reattached if removed; sets globals indicating background process state. cleanup.js sets global.allowBackgroundWindowDestruction = true at quit, switches destruction calls to use the preserved original destroy when available, and adds a 2-second timeout fallback that forces destruction if shutdown doesn't complete.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • RuntimeTerror10
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding logic to keep the background process running when the debug console is closed, which aligns with the modifications across cleanup.js and startBackgroundProcess.js.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ENGG-5314_background_debug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/actions/cleanup.js (1)

17-49: ⚠️ Potential issue | 🟡 Minor

ipcMain.once("shutdown-success") listener is never removed on the timeout path.

If the timeout fires first (because the background process never sends "shutdown-success"), the promise resolves but the ipcMain.once("shutdown-success") listener remains registered. If a stale "shutdown-success" message arrives later (e.g., from a new background window in a subsequent session), it will invoke the old callback on a now-destroyed window reference.

Remove the listener when the timeout fires.

Sketch
+      const onShutdownSuccess = () => {
+        clearTimeout(shutdownTimeout);
+        // ... destroy + resolve
+      };
+      ipcMain.once("shutdown-success", onShutdownSuccess);
+
       const shutdownTimeout = setTimeout(() => {
+        ipcMain.removeListener("shutdown-success", onShutdownSuccess);
         // ... destroy + resolve
       }, 2000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/cleanup.js` around lines 17 - 49, The
ipcMain.once("shutdown-success") handler registered in getReadyToQuitApp can
remain after the timeout fires; change the logic to capture the listener
callback in a variable (the function passed to ipcMain.once) and, inside the
timeout fallback before resolving, remove that listener (using
ipcMain.removeListener or ipcMain.off with the same event name and captured
callback) so the stale callback cannot run later against
global.backgroundWindow; keep the existing destroy/_originalDestroy calls and
resolve behavior intact.
🧹 Nitpick comments (3)
src/main/actions/startBackgroundProcess.js (3)

72-120: Consider extracting lifecycle protection into a helper function.

The close handler, destroy override, and removeAllListeners override collectively form a single "protect window from destruction" concern spread across ~50 lines in the middle of startBackgroundProcess. Extracting this into a named helper (e.g., protectWindowFromDestruction(window)) would improve readability and make it testable independently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 72 - 120, Extract
the window-lifecycle protection logic into a helper named
protectWindowFromDestruction(window): move the closeHandler, its attachment, the
_preventCloseHandler assignment, the overridden destroy (using originalDestroy
and _originalDestroy), and the overridden removeAllListeners (using
originalRemoveAllListeners) into that function; have startBackgroundProcess call
protectWindowFromDestruction(backgroundWindow) before
setupIPCForwardingToBackground and before setting the global flags so behavior
is unchanged but the lifecycle-protection concern is isolated and testable.

72-80: event.returnValue = false is unnecessary in a close event handler.

In Electron, event.preventDefault() on the close event is sufficient to prevent the window from closing. event.returnValue is only meaningful in synchronous IPC or beforeunload handlers. Line 75 can be removed for clarity.

Proposed fix
     const closeHandler = (event) => {
       event.preventDefault();
-      event.returnValue = false;
       if (!backgroundWindow.isDestroyed()) {
         backgroundWindow.hide();
       }
-      return false;
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 72 - 80, The close
handler sets event.returnValue which is unnecessary for the Electron 'close'
event; update the closeHandler (the function assigned where backgroundWindow is
handled) to remove the line setting event.returnValue and rely solely on
event.preventDefault() and backgroundWindow.hide(); keep the checks for
backgroundWindow.isDestroyed() and the final return false if desired, but delete
the redundant event.returnValue assignment to simplify the handler.

103-113: Consider a less invasive approach to protecting the close handler.

Monkey-patching removeAllListeners is fragile and brittle across Electron upgrades. While removeListener is not currently patched or called in the codebase, this approach relies on incomplete protections that could be bypassed.

Instead of patching core EventEmitter methods, document that the close handler must not be removed, or refactor to guard the removal at the point where it happens (e.g., skip removing 'close' listeners in any code that calls removeAllListeners).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 103 - 113, You
currently monkey-patch backgroundWindow.removeAllListeners to protect the
closeHandler, which is fragile; instead revert that override and either (A) add
a small safe utility or guard at call sites that remove listeners so they skip
the 'close' event (e.g., replace calls to
backgroundWindow.removeAllListeners(...) with a safeRemoveAllListeners(target,
eventName) helper that delegates except when eventName is 'close' or undefined),
or (B) centralize listener cleanup in a single function that knows not to remove
the 'close' listener; update/remove the override of removeAllListeners and
ensure places that previously cleared listeners call the new safe helper or the
centralized cleanup, referencing backgroundWindow, closeHandler,
removeAllListeners, and removeListener as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/actions/cleanup.js`:
- Around line 25-45: The shutdown handler for ipcMain.once("shutdown-success")
can race with the timeout and call destroy/resolve twice; store the timeout
handle returned by setTimeout, then in the "shutdown-success" callback first
clearTimeout(timeoutHandle) and check global.backgroundWindow &&
!global.backgroundWindow.isDestroyed() before calling either
global.backgroundWindow._originalDestroy() or global.backgroundWindow.destroy();
likewise keep the existing isDestroyed() guard in the timeout path and only call
resolve() once per path after performing the guarded destroy. Ensure you
reference ipcMain.once("shutdown-success"), the timeout handle from
setTimeout(...), global.backgroundWindow, _originalDestroy, isDestroyed(), and
resolve() when applying the fix.

In `@src/main/actions/startBackgroundProcess.js`:
- Around line 87-98: The overridden destroy implementation calls
originalDestroy() unconditionally when global.allowBackgroundWindowDestruction
is true which can throw if the window is already destroyed; update the
backgroundWindow.destroy override (the function that binds originalDestroy and
replaces backgroundWindow.destroy) to check backgroundWindow.isDestroyed()
before calling originalDestroy(), mirroring the other guard: only call
originalDestroy() when global.allowBackgroundWindowDestruction is true AND
!backgroundWindow.isDestroyed(), otherwise fall back to hiding when not
destroyed.

---

Outside diff comments:
In `@src/main/actions/cleanup.js`:
- Around line 17-49: The ipcMain.once("shutdown-success") handler registered in
getReadyToQuitApp can remain after the timeout fires; change the logic to
capture the listener callback in a variable (the function passed to
ipcMain.once) and, inside the timeout fallback before resolving, remove that
listener (using ipcMain.removeListener or ipcMain.off with the same event name
and captured callback) so the stale callback cannot run later against
global.backgroundWindow; keep the existing destroy/_originalDestroy calls and
resolve behavior intact.

---

Nitpick comments:
In `@src/main/actions/startBackgroundProcess.js`:
- Around line 72-120: Extract the window-lifecycle protection logic into a
helper named protectWindowFromDestruction(window): move the closeHandler, its
attachment, the _preventCloseHandler assignment, the overridden destroy (using
originalDestroy and _originalDestroy), and the overridden removeAllListeners
(using originalRemoveAllListeners) into that function; have
startBackgroundProcess call protectWindowFromDestruction(backgroundWindow)
before setupIPCForwardingToBackground and before setting the global flags so
behavior is unchanged but the lifecycle-protection concern is isolated and
testable.
- Around line 72-80: The close handler sets event.returnValue which is
unnecessary for the Electron 'close' event; update the closeHandler (the
function assigned where backgroundWindow is handled) to remove the line setting
event.returnValue and rely solely on event.preventDefault() and
backgroundWindow.hide(); keep the checks for backgroundWindow.isDestroyed() and
the final return false if desired, but delete the redundant event.returnValue
assignment to simplify the handler.
- Around line 103-113: You currently monkey-patch
backgroundWindow.removeAllListeners to protect the closeHandler, which is
fragile; instead revert that override and either (A) add a small safe utility or
guard at call sites that remove listeners so they skip the 'close' event (e.g.,
replace calls to backgroundWindow.removeAllListeners(...) with a
safeRemoveAllListeners(target, eventName) helper that delegates except when
eventName is 'close' or undefined), or (B) centralize listener cleanup in a
single function that knows not to remove the 'close' listener; update/remove the
override of removeAllListeners and ensure places that previously cleared
listeners call the new safe helper or the centralized cleanup, referencing
backgroundWindow, closeHandler, removeAllListeners, and removeListener as
needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/main/actions/startBackgroundProcess.js (3)

73-80: event.returnValue = false and return false are ineffective on BrowserWindow's close event.

event.preventDefault() on line 74 is the correct and sufficient way to cancel a BrowserWindow close event. event.returnValue is meaningful for webContents' beforeunload events, not BrowserWindow lifecycle events, and return false has no effect in Node EventEmitter handlers. Both can be removed to avoid misleading future readers.

Suggested cleanup
     const closeHandler = (event) => {
       event.preventDefault();
-      event.returnValue = false;
       if (!backgroundWindow.isDestroyed()) {
         backgroundWindow.hide();
       }
-      return false;
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 73 - 80, The
closeHandler currently sets event.returnValue = false and returns false which
are ineffective for a BrowserWindow 'close' event; update closeHandler to rely
solely on event.preventDefault() to cancel the close and then hide the
backgroundWindow if it's not destroyed (keep the existing
backgroundWindow.hide() call guarded by backgroundWindow.isDestroyed()); remove
the lines setting event.returnValue and the final return false so the handler
only calls event.preventDefault() and performs the hide action.

103-113: removeListener/off are not overridden, leaving a gap in close-handler protection.

If the intent is to prevent external code from stripping the close handler, note that removeListener('close', handler) and off('close', handler) can still remove it. Whether this matters depends on your threat model — if only removeAllListeners is called by the code paths you're guarding against, this is fine. Otherwise, consider wrapping off/removeListener similarly for the 'close' event.

Also, originalRemoveAllListeners is already .bind(backgroundWindow) on line 104, so the .call(backgroundWindow, ...) on lines 108 and 112 is redundant — you can call originalRemoveAllListeners(eventName) directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 103 - 113, The
override only protects removeAllListeners; to fully guard the close handler on
backgroundWindow, also wrap/remove override removeListener and off so attempts
to remove the 'close' handler are ignored or re-attached: intercept
backgroundWindow.removeListener and backgroundWindow.off, check if eventName ===
'close' (and optionally match handler === closeHandler) then no-op or re-attach
closeHandler, otherwise delegate to the original bound functions; also simplify
calls to originalRemoveAllListeners by invoking
originalRemoveAllListeners(eventName) directly (remove the redundant
.call(backgroundWindow, ...)) and keep references to the originals
(originalRemoveAllListeners, originalRemoveListener, originalOff) when adding
these overrides.

119-120: Consider documenting the distinction between the two global flags.

global.isBackgroundProcessActive (line 119) and global.backgroundProcessStarted (line 120) convey subtly different semantics (current state vs. ever-started). A brief inline comment clarifying each flag's purpose would help future maintainers avoid misuse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/actions/startBackgroundProcess.js` around lines 119 - 120, Add
concise inline comments where global.isBackgroundProcessActive and
global.backgroundProcessStarted are set (in the startBackgroundProcess flow)
explaining that isBackgroundProcessActive denotes the current run-time state
(true while the background loop/process is running) and backgroundProcessStarted
denotes an "ever-started" flag (true once the process has been initiated at
least once), so maintainers know one is transient/current-state and the other is
a one-time marker used for startup logic and metrics. Ensure comments are placed
immediately above the two assignments and mention intended usage (e.g., toggled
on stop vs. never reset) to prevent misuse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/actions/startBackgroundProcess.js`:
- Around line 73-80: The closeHandler currently sets event.returnValue = false
and returns false which are ineffective for a BrowserWindow 'close' event;
update closeHandler to rely solely on event.preventDefault() to cancel the close
and then hide the backgroundWindow if it's not destroyed (keep the existing
backgroundWindow.hide() call guarded by backgroundWindow.isDestroyed()); remove
the lines setting event.returnValue and the final return false so the handler
only calls event.preventDefault() and performs the hide action.
- Around line 103-113: The override only protects removeAllListeners; to fully
guard the close handler on backgroundWindow, also wrap/remove override
removeListener and off so attempts to remove the 'close' handler are ignored or
re-attached: intercept backgroundWindow.removeListener and backgroundWindow.off,
check if eventName === 'close' (and optionally match handler === closeHandler)
then no-op or re-attach closeHandler, otherwise delegate to the original bound
functions; also simplify calls to originalRemoveAllListeners by invoking
originalRemoveAllListeners(eventName) directly (remove the redundant
.call(backgroundWindow, ...)) and keep references to the originals
(originalRemoveAllListeners, originalRemoveListener, originalOff) when adding
these overrides.
- Around line 119-120: Add concise inline comments where
global.isBackgroundProcessActive and global.backgroundProcessStarted are set (in
the startBackgroundProcess flow) explaining that isBackgroundProcessActive
denotes the current run-time state (true while the background loop/process is
running) and backgroundProcessStarted denotes an "ever-started" flag (true once
the process has been initiated at least once), so maintainers know one is
transient/current-state and the other is a one-time marker used for startup
logic and metrics. Ensure comments are placed immediately above the two
assignments and mention intended usage (e.g., toggled on stop vs. never reset)
to prevent misuse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant